
struct VertexData {
    float4 vPosition : POSITION;
    float4 vNormal: NORMAL;
    float2 vTextureCoordinate: TEXCOORD0;
};

struct PixelData
{
    float4 vPosition : POSITION;
    
    float2 vTextureCoordinate: TEXCOORD0;
    float3 vPosition3D : TEXCOORD1;
    float3 vNormal : TEXCOORD2;
};

struct FrameData
{
    float4 cColor : COLOR;
};

//------- Constants --------

// These are the matrixes
float4x4 View;
float4x4 Projection;
float4x4 World;
float4x4 WorldNormal;

// The texture for the model
texture Texture;

// The Ambient Light Color (RGB)
float3 cAmbientLightColor;

/*

*/
float3 uDirectionalLightDirection;
float3 cDirectionalLightColor;

float3 vPointLightPosition;
float3 cPointLightColor;
float3 dPointLightAttenuation;

float3 vSpotLightPosition;
float3 cSpotLightColor;
float3 dSpotLightAttenuation;
float3 uSpotLightDirection;
float SpotLightDropoff;
float SpotLightCutoff;

float3 cMaterialDiffuse;
float3 cMaterialSpecular;
float MaterialSpecularFalloff;

float3 vViewerLocation;

sampler TextureSampler = sampler_state { texture = <Texture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;};


PixelData GetPixelDataFromVertexData(VertexData vertex) {
    PixelData pixel = (PixelData)0;
    float4x4 ViewProjection = mul (View, Projection);
    float4x4 WorldViewProjection = mul (World, ViewProjection);

    pixel.vPosition = mul(vertex.vPosition, WorldViewProjection);
    pixel.vPosition3D = mul(vertex.vPosition, World);
    pixel.vTextureCoordinate = vertex.vTextureCoordinate;

    pixel.vNormal = normalize(mul(vertex.vNormal, WorldNormal));	

    return pixel;
}

FrameData GetFrameData(PixelData pixel, float3 cLightIntensity) {
    FrameData Output = (FrameData)0;

    Output.cColor = tex2D(TextureSampler, pixel.vTextureCoordinate);
    Output.cColor.rgb *= cLightIntensity;

    return Output;
}

// CALCULATES THE DIFFUSE AND SPECULAR COMPONENTS

float3 Diffuse(float3 cLightIntensity, float3 uLightToPoint, float3 uNormal, float3 uPointToViewer) {
    float angle = dot(uNormal, -uLightToPoint);
    // check to see if the light is coming in from behind the face
    if(angle < 0) {
        return float3(0, 0, 0);
    }
    
    return angle * cMaterialDiffuse * cLightIntensity;
}

float3 Specular(float3 cLightIntensity, float3 uLightToPoint, float3 uNormal, float3 uPointToViewer) {
    float angle = dot(uNormal, -uLightToPoint);
    // check to see if the light is coming in from behind the face
    if(angle < 0) {
        return float3(0, 0, 0);
    }

    // Reflect the light ray across the normal
    float3 uReflection = normalize(reflect(uLightToPoint, uNormal));

    // Get the angle of that ray with the viewer
    float reflectedAngle = dot(uReflection, uPointToViewer);

    // If the viewer angle is less than zero, that means we see none of the reflection
    if(reflectedAngle < 0) {
        return float3(0, 0, 0);
    }

    return pow(abs(reflectedAngle), MaterialSpecularFalloff) * cLightIntensity * cMaterialSpecular;
}

float3 PhongLightCalculation(float3 vPosition, float3 uNormal, float3 cIntensity, float3 uLightToPoint) {

    float3 uPointToViewer = normalize(vViewerLocation - vPosition);

    return saturate(
        saturate(Diffuse(cIntensity, uLightToPoint, uNormal, uPointToViewer)) + 
        saturate(Specular(cIntensity, uLightToPoint, uNormal, uPointToViewer))
    );
}

// BEING LIGHT INTENSITY CALCULATORS

float3 AmbientLightIntensity(float3 vPosition, float3 uNormal) {
    return cAmbientLightColor;
}

float3 DirectionalLightIntensity(float3 vPosition, float3 uNormal) {
    return PhongLightCalculation(vPosition, uNormal, cDirectionalLightColor, uDirectionalLightDirection);
}

float3 PointLightIntensity(float3 vPosition, float3 uNormal) {
    float3 vLightToPoint = vPosition - vPointLightPosition;
    float3 uLightToPoint = normalize(vLightToPoint);
    
    float distance = length(vLightToPoint);
    float3 dDropoffs = float3(1, distance, distance * distance);

    // this effectively calcuates: attenuation = a + b * distance + c * distance^2 
    // where a is the constant attenuation, b is the linear attenuation, and c is quadratic dropoff
    float attunuation = dot(dPointLightAttenuation, dDropoffs);

    float3 cIntensity = (cPointLightColor / attunuation);

    return PhongLightCalculation(vPosition, uNormal, cIntensity, uLightToPoint);
}

float3 SpotLightIntensity(float3 vPosition, float3 uNormal) {
    float3 vLightToPoint = vPosition - vSpotLightPosition;
    float3 uLightToPoint = normalize(vLightToPoint);
    
    float angle = dot(uSpotLightDirection, uLightToPoint);
    if(angle < SpotLightCutoff) {
        return float3(0, 0, 0);
    }
    
    float distance = length(vLightToPoint);
    float3 dDropoffs = float3(1, distance, distance * distance);

    // this effectively calcuates: attenuation = a + b * distance + c * distance^2 
    // where a is the constant attenuation, b is the linear attenuation, and c is quadratic dropoff
    float attunuation = dot(dSpotLightAttenuation, dDropoffs);

    float3 cIntensity = (cSpotLightColor / attunuation) * pow(abs(angle), SpotLightDropoff);

    return PhongLightCalculation(vPosition, uNormal, cIntensity, uLightToPoint);
}

// BEGIN THE VERTEX SHADERS

PixelData TexturedVS(VertexData vertex) {	
    return GetPixelDataFromVertexData(vertex);    
}

// BEGIN THE PIXEL SHADERS


FrameData RenderBlackPS(PixelData pixel) 
{
	return (FrameData)float4(0, 0, 0, 1);
}

FrameData TexturedAmbientPS(PixelData pixel) {
    float3 cLightIntensity = AmbientLightIntensity(pixel.vPosition3D, normalize(pixel.vNormal));
    return GetFrameData(pixel, cLightIntensity);
}

FrameData TexturedDirectionalPS(PixelData pixel) {
    float3 cLightIntensity = DirectionalLightIntensity(pixel.vPosition3D, normalize(pixel.vNormal));
    return GetFrameData(pixel, cLightIntensity);
}

FrameData TexturedPointPS(PixelData pixel) {
    float3 cLightIntensity = PointLightIntensity(pixel.vPosition3D, normalize(pixel.vNormal));
    return GetFrameData(pixel, cLightIntensity);
}

FrameData TexturedSpotPS(PixelData pixel) {
    float3 cLightIntensity = SpotLightIntensity(pixel.vPosition3D, normalize(pixel.vNormal));
    return GetFrameData(pixel, cLightIntensity);
}


// BEGIN THE TECHNIQUES

technique RenderBlack
{
	pass Pass0
	{   
		VertexShader = compile vs_2_0 TexturedVS();
		PixelShader  = compile ps_2_0 RenderBlackPS();
	}
}

technique TexturedAmbient {
	pass Pass0 {   
		VertexShader = compile vs_2_0 TexturedVS();
		PixelShader  = compile ps_2_0 TexturedAmbientPS();
	}
}

technique TexturedDirectional {
    pass Pass0 {   
        VertexShader = compile vs_2_0 TexturedVS();
        PixelShader  = compile ps_2_0 TexturedDirectionalPS();
    }
}

technique TexturedPoint {
    pass Pass0 {
        VertexShader = compile vs_2_0 TexturedVS();
        PixelShader  = compile ps_2_0 TexturedPointPS();
    }
}

technique TexturedSpot {
    pass Pass0 {
        VertexShader = compile vs_2_0 TexturedVS();
        PixelShader  = compile ps_2_0 TexturedSpotPS();
    }
}